/**
    @file pixelroom.cpp
    @brief Game room
*/
#include "pixel/pixelroom.hpp"

pm3::PIXELROOM::PIXELROOM() {
    m_object = NULL;
    m_zone = NULL;
    m_pvs_zone = NULL;

    m_render_mode = RM_PVS;

    for (int i=0; i<pm3::util::DID_LAST; i++)
        m_portals [i] = NULL;

    m_show_debug = false;
    m_debug_box = NULL;
    m_debug_node = NULL;
}

pm3::PIXELROOM::~PIXELROOM() {
    // Let Ogre handle the deletion of nodes
    m_node = NULL;
    // Let PCZSceneManager handle the deletion of zones
    m_zone = NULL;
    // Unload everything else
    unload();
}

void pm3::PIXELROOM::unload() {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    int i;

/*    if (m_debug_node) {
//        m_debug_node->setVisible( false );
        intf->get_scenemgr()->destroyEntity( m_debug_box->getName() );
        intf->get_scenemgr()->destroySceneNode( m_debug_node->getName() );
        m_show_debug = false;
        m_debug_node = NULL;
        m_debug_box = NULL;
    }*/

    pm3::PIXELNODE::unload();

    if (intf && intf->get_scenemgr() && m_zone) {
        for (i=0; i<pm3::util::DID_LAST; i++) {
            if (m_portals [i]) {
                // Aand destroy this one as well
                intf->get_scenemgr()->destroyPortal( m_portals [i] );
                // Make sure we clear the pointers
                m_portals [i] = NULL;
            }
        }

//        if (m_zone->getEnclosureNode())
//            intf->get_world_node()->removeAndDestroyChild( m_zone->getEnclosureNode()->getName() );

        //! \bug Crashes here
//        intf->get_scenemgr()->destroyZone( m_zone, true );
    }

    m_zone = NULL;

    m_variables.clear();
}

std::string pm3::PIXELROOM::get_variable( const std::string &r_var ) {
    std::map<std::string, std::string>::iterator it;

    it = m_variables.find( r_var );
    // Found it?
    if (it != m_variables.end()) {
        return it->second;
    }

    return "";
}

void pm3::PIXELROOM::set_variable( const std::string &r_var, const std::string &r_val ) {
    m_variables [r_var] = r_val;
}

void pm3::PIXELROOM::init( Ogre::Real f_size, PIXELOBJ *p_obj ) {
//    LOGGER *log = LOGGER::getSingletonPtr();

    m_size = f_size;

    pm3::PIXELNODE::init( p_obj, m_grid_pos * m_size );
}

void pm3::PIXELROOM::init_portals( Ogre::PCZone *p_zone ) {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();

    //! \todo Interface can't fail - there's no need for any error checks of that sort

    // Do we have a zone?
    if (!p_zone) {
        if (log)
            log->report( "Pixel", "E: Trying to initialize portals for a room with no zone\n" );
        return;
    }
    // Do we have a room object?
    if (!m_object) {
        if (log)
            log->report( "Pixel", "E: Trying to initialize portals for a room with no pixel object\n" );
        return;
    }
    // Do we have a node?
    if (!m_node) {
        if (log)
            log->report( "Pixel", "E: Trying to initialize portals for a room with no pixel object\n" );
        return;
    }
    // Do we have an interface to scenemanager?
    if (!intf || !intf->get_scenemgr()) {
        if (log)
            log->report( "Pixel", "E: No interface to scenemanager, needed for initializing room portals\n" );
        return;
    }

    Ogre::Portal *portal = NULL;
    Ogre::Vector3 corners [4];
    int directions = m_object->get_directions();
    std::string name = "Portal";
    // Add positional suffix
    name += pm3::util::pos_suffix( get_grid_pos() );

    //! \todo Add positional suffixes to all room node names

    m_zone = p_zone;
    m_pvs_zone = m_zone;
    // Update zone
    update_zone();

    // Has a north direction?
    if ( ( directions & pm3::util::D_N ) != 0 ) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_N] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_N" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3( 1,  1, -1 );
            corners [1] = Ogre::Vector3( -1, 1, -1 );
            corners [2] = Ogre::Vector3( -1, -1, -1 );
            corners [3] = Ogre::Vector3(  1, -1, -1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_N] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_N] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_N] );
            m_portals [pm3::util::DID_N] = NULL;
        }
    }

    // Has an east direction?
    if ( ( directions & pm3::util::D_E ) != 0) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_E] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_E" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3(  1,  1,  1 );
            corners [1] = Ogre::Vector3(  1,  1, -1 );
            corners [2] = Ogre::Vector3(  1, -1, -1 );
            corners [3] = Ogre::Vector3(  1, -1,  1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_E] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_E] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_E] );
            m_portals [pm3::util::DID_E] = NULL;
        }
    }

    // Has a south direction?
    if ( ( directions & pm3::util::D_S ) != 0) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_S] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_S" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3( -1,  1, 1 );
            corners [1] = Ogre::Vector3(  1,  1, 1 );
            corners [2] = Ogre::Vector3(  1, -1, 1 );
            corners [3] = Ogre::Vector3( -1, -1, 1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_S] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_S] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_S] );
            m_portals [pm3::util::DID_S] = NULL;
        }
    }

    // Has a west direction?
    if ( ( directions & pm3::util::D_W ) != 0) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_W] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_W" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3( -1,  1, -1 );
            corners [1] = Ogre::Vector3( -1,  1,  1 );
            corners [2] = Ogre::Vector3( -1, -1,  1 );
            corners [3] = Ogre::Vector3( -1, -1, -1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_W] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_W] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_W] );
            m_portals [pm3::util::DID_W] = NULL;
        }
    }

    // Has an up direction?
    if ( ( directions & pm3::util::D_U ) != 0) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_U] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_U" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3( -1,  1, -1 );
            corners [1] = Ogre::Vector3(  1,  1, -1 );
            corners [2] = Ogre::Vector3(  1,  1,  1 );
            corners [3] = Ogre::Vector3( -1,  1,  1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_U] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_U] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_U] );
            m_portals [pm3::util::DID_U] = NULL;
        }
    }

    // Has a down direction?
    if ( ( directions & pm3::util::D_D ) != 0) {
        // Create a portal if there is none
        if ( !m_portals [pm3::util::DID_D] ) {
            // Create a portal
            portal = intf->get_scenemgr()->createPortal( name + "_D" );
            // Devise portal corner points
            corners [0] = Ogre::Vector3( -1, -1,  1 );
            corners [1] = Ogre::Vector3(  1, -1,  1 );
            corners [2] = Ogre::Vector3(  1, -1, -1 );
            corners [3] = Ogre::Vector3( -1, -1, -1 );
            // Set portal corner points
            portal->setCorners( corners );
            // Associate the portal with the scenenode
            portal->setNode( m_node );
            // Add the portal to the zone
            m_zone->_addPortal( portal );
            // Update portal corners
            portal->updateDerivedValues();
            // And store its pointer
            m_portals [pm3::util::DID_D] = portal;
        }
    } else {
        // Remove the portal (if any)
        if ( m_portals [pm3::util::DID_D] ) {
            //! \todo Make sure there are no memory leaks
            intf->get_scenemgr()->destroyPortal( m_portals [pm3::util::DID_D] );
            m_portals [pm3::util::DID_D] = NULL;
        }
    }
}

Ogre::Portal *pm3::PIXELROOM::get_portal( int i_dir ) const {
    return m_portals [ pm3::util::get_direction_id( i_dir ) ];
}

void pm3::PIXELROOM::remove_portal( int i_dir ) {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();

    if (m_zone && m_portals [ pm3::util::get_direction_id( i_dir ) ]) {
        intf->get_scenemgr()->destroyPortal( m_portals [ pm3::util::get_direction_id( i_dir ) ] );

        m_portals [ pm3::util::get_direction_id( i_dir ) ] = NULL;
    }
}

void pm3::PIXELROOM::clear_portals() {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();

    for (int i=0; i<pm3::util::DID_LAST; i++) {
        if (m_portals [i]) {
            intf->get_scenemgr()->destroyPortal( m_portals [i] );

            m_portals [i] = NULL;
        }
    }
}

void pm3::PIXELROOM::debug_render() {
//    pm3::dbg::PORTAL_DRAWER::getSingleton().drawNode( m_node );

    for (int i=0; i<pm3::util::DID_LAST; i++) {
        if (m_portals [i])
            pm3::dbg::PORTAL_DRAWER::getSingleton().drawPortal( m_portals [i] );
    }
}

void pm3::PIXELROOM::update_zone() {
    LOGGER *log = LOGGER::getSingletonPtr();
    std::list< Ogre::PCZSceneNode * > nodes;
    Ogre::PCZSceneNode *node = NULL;

    if (!m_node || !m_zone)
        return;

    // Remove all nodes from the zone (cause we're going to add them again)
    for (size_t i=0; i<m_nodes.size(); i++) {
        if ( m_nodes [i] )
            m_zone->removeNode( ( Ogre::PCZSceneNode *) m_nodes [i] );
    }

    // Take the main node as the first one
    nodes.push_back( m_node );
    // And add it to the zone with all of its children
    while (!nodes.empty()) {
        node = nodes.front();
        nodes.pop_front();

        if (!node)
            continue;

        // Set subnode homezone the same with its parent
        node->setHomeZone( m_zone );
        // Add the subnodes to the same zone
        m_zone->_addNode( node );

        // Add node children for the next wave
        for (unsigned short i=0; i<node->numChildren(); i++)
            nodes.push_back( ( Ogre::PCZSceneNode * ) node->getChild( i ) );
    }
}

void pm3::PIXELROOM::set_render_mode( RENDER_MODE e_rm ) {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();

    // Do we have an interface to scenemanager?
    if (!intf || !intf->get_scenemgr()) {
        if (log)
            log->report( "Pixel", "E: No interface to scenemanager, needed for initializing room portals\n" );
        return;
    }

    switch ( e_rm ) {
        // We about to hide the room?
        case RM_HIDE:            
            m_zone = intf->get_scenemgr()->getZoneByName( PM_HIDEZONE );
            break;

        // About to turn portal-connected-zone rendering on?
        case RM_PVS:
            m_zone = m_pvs_zone;
            break;

        // Godlike see-it-all?
        case RM_SHOW:
            m_zone = intf->get_scenemgr()->getDefaultZone();
            break;

        default: break;
    }

    // Propagate the new zone down to the children
    update_zone();
}

std::string pm3::PIXELROOM::get_name() const {
    std::string str = "Room_";
    str += pm3::util::to_str( m_grid_pos.y ) + "_";
    str += pm3::util::to_str( m_grid_pos.x ) + "_";
    str += pm3::util::to_str( m_grid_pos.z );

    return str;
}

void pm3::PIXELROOM::show_debug_box( bool b_show ) {
    pm3::INTERFACE *intf = pm3::INTERFACE::getSingletonPtr();
    LOGGER *log = LOGGER::getSingletonPtr();
    static std::string mesh = "roombox.mesh";
    Ogre::SubEntity *subent = NULL;
    Ogre::Material *mat = NULL, *newmat = NULL;
    std::string name, matname;

    m_show_debug = b_show;

    if (!m_node)
        return;

    if (!b_show && m_debug_node) {
        // Hide the debug node
        m_debug_node->setVisible( false );
    } else if (b_show) {
        // Make a name for the debug box
        name = get_name() + "_Box";

        // Create debug node if needed
        if (!m_debug_node) {
            // Create it as a child of the global world node
            // so that it would be unaffected by room node reinitializations
            m_debug_node = ( Ogre::PCZSceneNode * ) intf->get_world_node()->createChildSceneNode( name.c_str() );
            // Copy its position and orientation from the room node
            m_debug_node->setPosition( m_node->getPosition() );
            m_debug_node->setOrientation( m_node->getOrientation() );
            // Put it to the default zone along with other debug stuff
            m_debug_node->setHomeZone( intf->get_scenemgr()->getDefaultZone() );
        }

        // Create debug box if needed
        if (!m_debug_box) {
            // Load the resource
            Ogre::MeshManager::getSingleton().load( mesh.c_str(), "Editor" );
            // Create the entity
            m_debug_box = intf->get_scenemgr()->createEntity( name.c_str(), mesh.c_str() );
            m_debug_box->setCastShadows( false );

            // Clone the material and change the color
            subent = m_debug_box->getSubEntity( 0 );
            if (subent) {
                mat = subent->getMaterial().get();
                if (mat) {
                    // Generate a new name for the material
                    matname = mat->getName() + std::string( "_" ) + name;
                    // Clone the material
                    newmat = mat->clone( matname.c_str() ).get();
                    // Change color
                    if (m_object)
                        newmat->setSelfIllumination( m_object->m_color );
                    // Set material
                    m_debug_box->setMaterialName( newmat->getName() );
                }
            }

            // And attach it
            m_debug_node->attachObject( m_debug_box );
        }

        // Set it visible if its color is not black
        if (m_debug_node) {
            if (!m_object || m_object->m_color == Ogre::ColourValue::Black)
                m_debug_node->setVisible( false );
            else
                m_debug_node->setVisible( true );
        }

/*        // And update its color (just in case)
        if (m_debug_box && m_object) {
            subent = m_debug_box->getSubEntity( 0 );

            if (subent) {
                mat = subent->getMaterial().get();

                if (mat) {
                    mat->setDiffuse( m_object->m_color );

                    // Set material
                    m_debug_box->setMaterialName( mat->getName() );
                }
            }
        }*/
    }
}

bool pm3::PIXELROOM::set_debug_box_color( const Ogre::ColourValue &r_col ) {
    Ogre::SubEntity *subent = NULL;
    Ogre::Material *mat = NULL;

    // Clone the material and change the color
    subent = m_debug_box->getSubEntity( 0 );
    if (subent) {
        mat = subent->getMaterial().get();
        if (mat) {
            // Change diffuse
            mat->setDiffuse( r_col );
        }
    }
}

